package cl.scene.badseeds;

import cl.scene.libbencode.ByteStringValue;
import cl.scene.libbencode.DictionaryType;
import cl.scene.libbencode.ListType;
import cl.scene.libbencode.IntegerValue;
import com.mongodb.*;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.MultiMap;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;

/**
 * Created by IntelliJ IDEA.
 * User: Patricio
 * Date: 27-11-2010
 * Time: 10:04:52 PM
 */
public class RequestHandler extends AbstractHandler {

    private Mongo mongoConnection;
    private DB database;


    public RequestHandler() throws UnknownHostException {
        mongoConnection = new Mongo("127.0.0.1", 27017);
        database = mongoConnection.getDB("badseeds");
    }

    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {


        // requested URI
        URI uri;

        try {
            long start = System.currentTimeMillis();
            uri = new URI(request.getRequestURI());
            System.out.println(request.getQueryString());
            if (uri.getPath().length() < 32) {
                throw new Exception("invalid passkey");
            }

            String passKey = uri.getPath().substring(3, 35);

            if (!checkForPassKey(passKey)) {
                throw new Exception("invalid passkey");
            }


            MultiMap m = new MultiMap<String>();
            org.eclipse.jetty.util.UrlEncoded.decodeTo(request.getQueryString(), m, "ASCII");
            m.put("requestIP", request.getRemoteAddr());
            checkForHashAndSavePeer(m);

            DictionaryType trackerResponse = createResponse(m);
            response.setContentType("text/html;charset=utf-8");
            response.setStatus(HttpServletResponse.SC_OK);
            trackerResponse.encodeAndWrite(response.getOutputStream());
            response.flushBuffer();
            long end = System.currentTimeMillis();
            System.out.println(" Request (ms) : " + (end-start));
        } catch (URISyntaxException e) {
            sendError(response, "bad request");
        } catch (Exception e) {
            sendError(response, e.getMessage());
            e.printStackTrace();
        }

/*        MultiMap m = new MultiMap<String>();
org.eclipse.jetty.util.UrlEncoded.decodeTo(request.getQueryString(), m , "ASCII");
String result = "not found";

DBCollection hashes = database.getCollection("hashes");
BasicDBObject query = new BasicDBObject();
query.put("info_hash", m.getString("info_hash").getBytes());

DBObject cur = hashes.findOne(query);

DictionaryType root = new DictionaryType();

if(cur != null) {
System.out.println ("Found!");
result = "found!";
root.add(new ByteStringValue("date"), new ByteStringValue(((Date) cur.get("date")).toString()));
} else {
cur = new BasicDBObject();
cur.put("info_hash", m.getString("info_hash").getBytes());
cur.put("date", new Date());
hashes.insert(cur);
}

root.add(new ByteStringValue("response"), new ByteStringValue(result));

request.setCharacterEncoding("ASCII");

long start = System.currentTimeMillis();

response.setContentType("text/html;charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);


//        byte[] t = m.getString("info_hash").getBytes();

baseRequest.setHandled(true);
root.encodeAndWrite(response.getOutputStream());
response.flushBuffer();
long end = System.currentTimeMillis();*/
    }

    private DictionaryType createResponse(MultiMap m) {
        DictionaryType response = new DictionaryType();
        ListType peerList = new ListType();
        DBCollection hashes = database.getCollection("hashes");
        String infoHash = (String) m.get("info_hash");
        DBObject query = new BasicDBObject();
        query.put("info_hash", m.get("info_hash"));
        BasicDBObject hash = (BasicDBObject) hashes.findOne(query);

        BasicDBList peers = (BasicDBList) hash.get("peer_list");
        int numwant = 50;

        if (m.getString("numwant") != null) {
            numwant = Integer.parseInt(m.getString("numwant"));
        }

        int count = 0;

        for (int i = peers.size() - 1; count < numwant && i >= 0; i--, count++) {
            BasicDBObject peerFromDB = (BasicDBObject) peers.get(i);

            DictionaryType peerDic = new DictionaryType();

            peerDic.add(new ByteStringValue("ip"), new ByteStringValue(peerFromDB.getString("ip")));
            peerDic.add(new ByteStringValue("port"), new IntegerValue(peerFromDB.getInt("port")));
            peerList.add(peerDic);
        }


        response.add(new ByteStringValue("interval"), new ByteStringValue("60"));
        response.add(new ByteStringValue("peers"), peerList);


        return response;
    }

    private boolean checkForHashAndSavePeer(MultiMap m) {
        DBCollection hashes = database.getCollection("hashes");
        String infoHash = (String) m.get("info_hash");
        DBObject query = new BasicDBObject();
        query.put("info_hash", m.get("info_hash"));
        DBObject hash = hashes.findOne(query);

        if (hash == null) {
            hash = new BasicDBObject();
            hash.put("info_hash", infoHash);
            BasicDBList peerList = new BasicDBList();
            BasicDBObject peer = new BasicDBObject();
            peer.put("ip", m.get("requestIP"));
            peer.put("port", Integer.parseInt(m.getString("port")));
            peerList.add(peer);
            hash.put("peer_list", peerList);
            hashes.insert(hash);
        } else {
            BasicDBList peerList = (BasicDBList) hash.get("peer_list");
            BasicDBObject peer = new BasicDBObject();
            BasicDBObject q = new BasicDBObject();
            peer.put("ip", m.get("requestIP"));
            peer.put("port", Integer.parseInt(m.getString("port")));
//            peerList.put("$addToSet", peer);
            q.put("$addToSet", new BasicDBObject("peer_list", peer));
            hashes.update(hash, q);
        }
        
        return true;
    }

    private boolean checkForPassKey(String passKey) {
        DBCollection passKeys = database.getCollection("passKeys");
        BasicDBObject query = new BasicDBObject();
        query.put("passkey", passKey);
        DBObject peer = passKeys.findOne(query);

        // we always accept passkey for now
        if (peer == null) {
            System.out.println("Pass Key not found");
            peer = new BasicDBObject();
            peer.put("passkey", passKey);
            passKeys.insert(peer);
        }

        return true;
    }

    private void sendError(HttpServletResponse response, String message) throws IOException {
        response.setContentType("text/html;charset=utf-8");
        response.setStatus(HttpServletResponse.SC_OK);
        DictionaryType rootResponse = new DictionaryType();
        rootResponse.add(new ByteStringValue("failure code"), new ByteStringValue("100"));
        rootResponse.add(new ByteStringValue("failure reason"), new ByteStringValue(message));
        rootResponse.encodeAndWrite(response.getOutputStream());
        response.flushBuffer();
    }

    public Mongo getMongoConnection() {
        return mongoConnection;
    }

    public void setMongoConnection(Mongo mongoConnection) {
        this.mongoConnection = mongoConnection;
    }

    public DB getDatabase() {
        return database;
    }

    public void setDatabase(DB database) {
        this.database = database;
    }
}
